/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.github.glomadrian.pathloading.utils;

import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.ScrollView;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

import java.math.BigDecimal;

/**
 * 侧边栏
 *
 * @since 2021-06-07
 */
public class SlideMenu extends StackLayout implements Component.TouchEventListener,
    ReboundHelper.ReboundOverListener, Component.ClickedListener {
    private static final int MDEFAULTTOUCHSLOP = 30;
    private static final int MTOUCHLAG = 200;
    private static final int MMAXMASKALPHA = 0xCC;
    private static final int NUMTWO = 2;
    private static final int NUMTHREE = 3;
    private static final int NUMFIFTEEN = 15;
    private Context mContext;
    private Component mMenuRoot;
    private Component mMenuContent;
    private Component mContentComponent;
    private ReboundHelper mReboundHelper;
    private ShapeElement mask;
    private Component[] mIgnoreComponents;
    private boolean isShowMenu;
    private int mMenuWidth;
    private int mTouchSlop;
    private float mLastX;
    private float mLastY;
    private long mStartTime;
    private boolean isDragMenu;
    private boolean isMeasureWidth;

    /**
     * SlideMenu构造器
     *
     * @param context
     */
    public SlideMenu(Context context) {
        super(context);
        init(context);
    }

    /**
     * 构造器
     *
     * @param context
     * @param attrSet
     */
    public SlideMenu(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(context);
    }

    /**
     * 构造器
     *
     * @param context
     * @param attrSet
     * @param styleName
     */
    public SlideMenu(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(context);
    }

    /**
     * 添加需要禁止滑动的组件
     *
     * @param component
     */
    public void addContentComponent(Component component) {
        mContentComponent = component;
    }

    private void init(Context context) {
        mContext = context;
        isShowMenu = false;
        isDragMenu = false;
        isMeasureWidth = false;
        mask = new ShapeElement();
        mReboundHelper = new ReboundHelper(this);
        setTouchSlopWithVp(MDEFAULTTOUCHSLOP);
    }

    /**
     * setTouchSlopWithVp
     *
     * @param touchSlop
     */
    public void setTouchSlopWithVp(int touchSlop) {
        mTouchSlop = AttrHelper.vp2px(touchSlop, mContext);
    }

    /**
     * setIgnoreComponents
     *
     * @param ignoreComponents
     */
    public void setIgnoreComponents(Component[] ignoreComponents) {
        this.mIgnoreComponents = ignoreComponents.clone();
    }

    /**
     * 初始化菜单
     *
     * @param content 菜单内容组件
     */
    public void initMenu(Component content) {
        mMenuContent = content;
        mMenuContent.addDrawTask(new DrawTask() {
            @Override
            public void onDraw(Component component, Canvas canvas) {
                if (!isMeasureWidth) {
                    isMeasureWidth = true;
                    mMenuWidth = component.getWidth();
                    mMenuContent.setTranslationX(-mMenuWidth);
                }
            }
        });

        ScrollView scrollView = new ScrollView(mContext);
        LayoutConfig layoutConfig = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
        scrollView.setLayoutConfig(layoutConfig);

        DirectionalLayout directionalLayout = new DirectionalLayout(mContext);
        directionalLayout.setLayoutConfig(layoutConfig);
        directionalLayout.addComponent(mMenuContent);
        scrollView.addComponent(directionalLayout);

        // 屏蔽下层滚动事件
        scrollView.setCanAcceptScrollListener((component, i, b) -> mMenuContent.getTranslationX() > -mMenuWidth);
        mMenuRoot = scrollView;
        mMenuRoot.setTouchEventListener(this);
        mMenuRoot.setBackground(mask);
        this.addComponent(mMenuRoot);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        float offsetX = new BigDecimal(touchEvent.getPointerScreenPosition(0).getX())
            .subtract(new BigDecimal(mLastX)).floatValue();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                mStartTime = System.currentTimeMillis();
                mLastX = touchEvent.getPointerScreenPosition(0).getX();
                mLastY = touchEvent.getPointerScreenPosition(0).getY();
                if (!isShowMenu && mIgnoreComponents != null) {
                    for (Component ignoreComponent : mIgnoreComponents) {
                        if (isTouchIgnorComponent(ignoreComponent)) {
                            return false;
                        }
                    }
                }
                if (!isShowMenu && mTouchSlop < mLastX) {
                    setStopEnable(true);
                    return false;
                }
                isDragMenu = !isShowMenu;
                break;
            case TouchEvent.POINT_MOVE:
                menuMove(touchEvent, offsetX);
                break;
            case TouchEvent.PRIMARY_POINT_UP: case TouchEvent.CANCEL:
                long offsetTime = System.currentTimeMillis() - mStartTime;
                float offsetFlag = offsetX - NUMFIFTEEN;
                if (offsetTime <= MTOUCHLAG && offsetFlag < 0) {
                    if (isShowMenu && mMenuWidth < mLastX) {
                        mReboundHelper.reboundX(mMenuContent, -mMenuWidth);
                        setStopEnable(false);
                        return true;
                    }
                }
                if (mMenuContent.getTranslationX() > (float) -mMenuWidth / NUMTWO) {
                    mReboundHelper.duration(mMenuContent.getTranslationX() / -mMenuWidth).reboundX(mMenuContent, 0);
                } else {
                    mReboundHelper.duration(new BigDecimal(1).subtract(new BigDecimal(mMenuContent.getTranslationX()).
                        divide(new BigDecimal(-mMenuWidth),NUMTWO)).floatValue()).reboundX(mMenuContent, -mMenuWidth);
                }
                break;
            default: break;
        }
        setStopEnable(false);
        return true;
    }

    private void menuMove(TouchEvent touchEvent, float offsetX) {
        mLastX = touchEvent.getPointerScreenPosition(0).getX();
        if (isShowMenu && !isDragMenu) {
            if (mMenuWidth > mLastX) {
                isDragMenu = true;
            }
        }
        if (isDragMenu) {
            if (new BigDecimal(mMenuContent.getTranslationX()).add(new BigDecimal(offsetX)).floatValue() > 0) {
                mMenuContent.setTranslationX(0);
            } else if (new BigDecimal(mMenuContent.getTranslationX()).
                add(new BigDecimal(offsetX)).floatValue() < -mMenuWidth) {
                mMenuContent.setTranslationX(-mMenuWidth);
            } else {
                update(new BigDecimal(mMenuContent.getTranslationX()).
                    add(new BigDecimal(offsetX)).floatValue(), new BigDecimal(0).floatValue());
            }
        }
    }

    private boolean isTouchIgnorComponent(Component ignoreComponent) {
        int[] position = ignoreComponent.getLocationOnScreen();
        if (position[0] <= mLastX && position[0] + ignoreComponent.getWidth() >= mLastX
            && position[1] <= mLastY && position[1] + ignoreComponent.getHeight() >= mLastY) {
            return true;
        }
        return false;
    }

    private void setStopEnable(boolean isEnable) {
        if (isEnable) {
            mMenuRoot.setClickable(false);
        } else {
            mMenuRoot.setClickedListener(this);
        }

        if (mContentComponent != null) {
            mContentComponent.setEnabled(isEnable);
        }
    }

    /**
     * 切换
     */
    public void toggle() {
        if (isShowMenu) {
            close();
        } else {
            open();
        }
    }

    /**
     * 打开
     */
    public void open() {
        if (!isShowMenu) {
            mReboundHelper.reboundX(mMenuContent, 0);
        }
    }

    /**
     * 关闭
     */
    public void close() {
        if (isShowMenu) {
            mReboundHelper.stop();
            mReboundHelper.duration(0).reboundX(mMenuContent, -mMenuWidth);
        }
    }

    @Override
    public void update(float offsetX, float offsetY) {
        mMenuContent.setTranslationX(offsetX);
        float value = new BigDecimal(mMenuContent.getTranslationX()).multiply(new BigDecimal(1.0F)).floatValue();
        float value1 = new BigDecimal(value).divide(new BigDecimal(-mMenuWidth),
            NUMTHREE, BigDecimal.ROUND_DOWN).floatValue();
        float value2 = new BigDecimal(value1).multiply(new BigDecimal(MMAXMASKALPHA)).floatValue();
        float value3 = new BigDecimal(MMAXMASKALPHA).subtract(new BigDecimal(value2)).floatValue();
        mask.setRgbColor(RgbColor.fromArgbInt(Color.argb(
            (int) value3,
            0, 0, 0)));
    }

    @Override
    public void over(float toX, float toY) {
        mMenuContent.setTranslationX(toX);
        if (mMenuContent.getTranslationX() == -mMenuWidth) {
            isShowMenu = false;
            setStopEnable(true);
        } else {
            isShowMenu = true;
        }
        isDragMenu = false;
    }

    @Override
    public void onClick(Component component) {
        // 屏蔽下层click事件
    }
}
